package pipe.gui.reachability; import net.sourceforge.jpowergraph.Edge; import net.sourceforge.jpowergraph.Node; import net.sourceforge.jpowergraph.defaults.DefaultGraph; import net.sourceforge.jpowergraph.layout.Layouter; import net.sourceforge.jpowergraph.layout.spring.SpringLayoutStrategy; import net.sourceforge.jpowergraph.lens.*; import net.sourceforge.jpowergraph.manipulator.dragging.DraggingManipulator; import net.sourceforge.jpowergraph.manipulator.popup.PopupManipulator; import net.sourceforge.jpowergraph.swing.SwingJGraphPane; import net.sourceforge.jpowergraph.swing.SwingJGraphScrollPane; import net.sourceforge.jpowergraph.swing.manipulator.SwingPopupDisplayer; import net.sourceforge.jpowergraph.swtswinginteraction.color.JPowerGraphColor; import pipe.gui.widget.GenerateResultsForm; import pipe.gui.widget.StateSpaceLoader; import pipe.gui.widget.StateSpaceLoaderException; import pipe.reachability.algorithm.*; import uk.ac.imperial.pipe.exceptions.InvalidRateException; import uk.ac.imperial.pipe.models.petrinet.PetriNet; import uk.ac.imperial.state.ClassifiedState; import uk.ac.imperial.state.Record; import javax.swing.*; import java.awt.Container; import java.awt.FileDialog; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.logging.Level; import java.util.logging.Logger; /** * GUI class used to display and run the results of reachability and coverability classes */ public class ReachabilityGraph { /** * Class logger */ private static final Logger LOGGER = Logger.getLogger(ReachabilityGraph.class.getName()); /** * Maximum number of states to graphically display */ private static final int MAX_STATES_TO_DISPLAY = 100; private JPanel panel1; /** * Contains the graph based results of state space exploration */ private JPanel resultsPanel; /** * Check box to determine if we include vanishing states in the exploration */ private JCheckBox includeVanishingStatesCheckBox; /** * For saving state space results */ private JButton saveButton; private JLabel textResultsLabel; private JPanel textResultsPanel; private JRadioButton reachabilityButton; private JRadioButton coverabilityButton; private JTextField maxStatesField; private JPanel stateLoadingPanel; private JPanel generatePanel; private DefaultGraph graph = new DefaultGraph(); private StateSpaceLoader stateSpaceLoader; /** * When selecting use current Petri net the petri net used will be * * @param loadDialog dialog * @param petriNet current petri net */ public ReachabilityGraph(FileDialog loadDialog, PetriNet petriNet) { stateSpaceLoader = new StateSpaceLoader(petriNet, loadDialog); setUp(); } /** * Set up action listeners */ private void setUp() { JPanel pane = setupGraph(); resultsPanel.add(pane); stateLoadingPanel.add(stateSpaceLoader.getMainPanel(), 0); ActionListener disableListener = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { reachabilityButton.setEnabled(false); coverabilityButton.setEnabled(false); includeVanishingStatesCheckBox.setEnabled(false); } }; ActionListener enableListener = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { reachabilityButton.setEnabled(true); coverabilityButton.setEnabled(true); includeVanishingStatesCheckBox.setEnabled(true); } }; // stateSpaceLoader.addPetriNetRadioListener(enableListener); stateSpaceLoader.addBinariesListener(disableListener); saveButton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { saveBinaryFiles(); } }); GenerateResultsForm resultsForm = new GenerateResultsForm(new GenerateResultsForm.GoAction() { @Override public void go(int threads) { calculateResults(threads); } }); generatePanel.add(resultsForm.getPanel()); } /** * Sets up the graph and returns the JPanel to add to * the resultsPanel * @return panel */ private JPanel setupGraph() { SwingJGraphPane pane = new SwingJGraphPane(graph); LensSet lensSet = new LensSet(); lensSet.addLens(new RotateLens()); lensSet.addLens(new TranslateLens()); lensSet.addLens(new ZoomLens()); CursorLens draggingLens = new CursorLens(); lensSet.addLens(draggingLens); lensSet.addLens(new TooltipLens()); lensSet.addLens(new LegendLens()); lensSet.addLens(new NodeSizeLens()); pane.setLens(lensSet); pane.addManipulator(new DraggingManipulator(draggingLens, -1)); pane.addManipulator(new PopupManipulator(pane, (TooltipLens) lensSet.getFirstLensOfType(TooltipLens.class))); pane.setNodePainter(TangibleStateNode.class, TangibleStateNode.getShapeNodePainter()); pane.setNodePainter(VanishingStateNode.class, VanishingStateNode.getShapeNodePainter()); pane.setEdgePainter(DirectedTextEdge.class, new PIPELineWithTextEdgePainter(JPowerGraphColor.BLACK, JPowerGraphColor.GRAY, false)); pane.setAntialias(true); pane.setPopupDisplayer(new SwingPopupDisplayer(new PIPESwingToolTipListener(), new PIPESwingContextMenuListener(graph, new LensSet(), new Integer[]{}, new Integer[]{}))); return new SwingJGraphScrollPane(pane, lensSet); } /** * Calculates the steady state exploration of a Petri net and stores its results * in a temporary file. * <p> * These results are then read in and turned into a graphical representation using mxGraph * which is displayed to the user * </p> * @param threads number of threads to use to explore the state space */ private void calculateResults(int threads) { try { StateSpaceExplorer.StateSpaceExplorerResults results = stateSpaceLoader.calculateResults(new StateSpaceLoader.ExplorerCreator() { @Override public ExplorerUtilities create(PetriNet petriNet) { return getExplorerUtilities(petriNet); } }, new StateSpaceLoader.VanishingExplorerCreator() { @Override public VanishingExplorer create(ExplorerUtilities utils) { return getVanishingExplorer(utils); } }, threads ); updateTextResults(results.numberOfStates, results.processedTransitions); if (results.numberOfStates <= MAX_STATES_TO_DISPLAY) { StateSpaceLoader.Results stateSpace = stateSpaceLoader.loadStateSpace(); updateGraph(stateSpace.records, stateSpace.stateMappings); } } catch (InvalidRateException | TimelessTrapException | IOException | InterruptedException | ExecutionException e) { LOGGER.log(Level.SEVERE, e.toString()); } catch (StateSpaceLoaderException e) { JOptionPane.showMessageDialog(panel1, e.getMessage(), "GSPN Analysis Error", JOptionPane.ERROR_MESSAGE); } } /** * Copies the temporary files to a permanent loaction */ private void saveBinaryFiles() { stateSpaceLoader.saveBinaryFiles(); } /** * Creates the explorer utilities based upon whether the coverability or reachability graph * is being generate * * @param petriNet to be displayed * @return explorer utilities for generating state space */ private ExplorerUtilities getExplorerUtilities(PetriNet petriNet) { if (coverabilityButton.isSelected()) { return new CoverabilityExplorerUtilities(new UnboundedExplorerUtilities(petriNet)); } return new BoundedExplorerUtilities(petriNet, Integer.valueOf(maxStatesField.getText())); } /** * Vanishing explorer is either a {@link pipe.reachability.algorithm.SimpleVanishingExplorer} if * vanishing states are to be included in the graph, else it is {@link pipe.reachability.algorithm.OnTheFlyVanishingExplorer} * * @param explorerUtilities utilities for analysis * @return vanishing explorer */ private VanishingExplorer getVanishingExplorer(ExplorerUtilities explorerUtilities) { if (includeVanishingStatesCheckBox.isSelected()) { return new SimpleVanishingExplorer(); } return new OnTheFlyVanishingExplorer(explorerUtilities); } /** * Updates the text results with the number of states and transitions * * @param states number of states * @param transitions number of transitions */ private void updateTextResults(int states, int transitions) { StringBuilder results = new StringBuilder(); results.append("Results: ").append(states).append(" states and ").append(transitions).append(" transitions"); textResultsLabel.setText(results.toString()); } /** * Updates the mxGraph to display the records * * @param records state transitions from a processed Petri net * @param stateMap map of stated */ private void updateGraph(Iterable<Record> records, Map<Integer, ClassifiedState> stateMap) { graph.clear(); Map<Integer, Node> nodes = getNodes(stateMap); Collection<Edge> edges = getEdges(records, nodes); graph.addElements(nodes.values(), edges); layoutGraph(); } /** * @param stateMap map of states * @return All nodes to be added to the graph */ private Map<Integer, Node> getNodes(Map<Integer, ClassifiedState> stateMap) { Map<Integer, Node> nodes = new HashMap<>(stateMap.size()); for (Map.Entry<Integer, ClassifiedState> entry : stateMap.entrySet()) { ClassifiedState state = entry.getValue(); int id = entry.getKey(); nodes.put(id, createNode(state, id)); } return nodes; } /** * All edges to be added to the graph * * @param records to add * @param nodes to add * @return edges */ private Collection<Edge> getEdges(Iterable<Record> records, Map<Integer, Node> nodes) { Collection<Edge> edges = new ArrayList<>(); for (Record record : records) { int state = record.state; for (Map.Entry<Integer, Double> entry : record.successors.entrySet()) { int succ = entry.getKey(); edges.add(new DirectedTextEdge(nodes.get(state), nodes.get(succ), String.format("%.2f", entry.getValue()))); } } return edges; } /** * Performs laying out of items on the graph */ private void layoutGraph() { Layouter layouter = new Layouter(new SpringLayoutStrategy(graph)); layouter.start(); } /** * @param state classified state to be turned into a graph node * @param id state integer id * @return Tangible or Vanishing state node corresponding to the state and its integer id representation */ private Node createNode(ClassifiedState state, int id) { String label = Integer.toString(id); String toolTip = state.toString(); if (state.isTangible()) { return new TangibleStateNode(label, toolTip); } return new VanishingStateNode(label, toolTip); } /** * Constructor deactivates use current petri net radio button since none is supplied. * * @param loadDialog file dialog */ public ReachabilityGraph(FileDialog loadDialog) { stateSpaceLoader = new StateSpaceLoader(loadDialog); setUp(); } /** * Main method for running this externally without PIPE * * @param args command line arguments */ public static void main(String[] args) { JFrame frame = new JFrame("ReachabilityGraph"); FileDialog selector = new FileDialog(frame, "Select petri net", FileDialog.LOAD); frame.setContentPane(new ReachabilityGraph(selector).panel1); frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); frame.pack(); frame.setVisible(true); } /** * @return main panel of the GUI */ public Container getMainPanel() { return panel1; } }